﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Model;

namespace BehaviorModel.Block.Finish
{
    public class FinishCollector : IFinishCollector
    {
        private static readonly Object monitor = new object();

        private ConcurrentBag<Request> _servicedRequests = new ConcurrentBag<Request>();
        private ConcurrentBag<Request> _rejectedRequests = new ConcurrentBag<Request>();

        #region Properties

        public long CountOfPassed
        {
            get { return CountOfServiced + CountOfRejected; }
        }

        public double AvgCommonServicedTime { get; private set; }

        public double AvgCommonWaitingTime { get; private set; }

        public double AvgCommonLiveTime
        {
            get { return AvgCommonServicedTime + AvgCommonWaitingTime; }
        }


        public long CountOfServiced { get; private set; }

        public double ServAvgCommonServicedTime { get; private set; }

        public double ServAvgCommonWaitingTime { get; private set; }

        public double ServAvgCommonLiveTime
        {
            get { return ServAvgCommonServicedTime + ServAvgCommonWaitingTime; }
        }


        public long CountOfRejected { get; private set; }

        public double RejAvgCommonServicedTime { get; private set; }

        public double RejAvgCommonWaitingTime { get; private set; }

        public double RejAvgCommonLiveTime
        {
            get { return RejAvgCommonServicedTime + RejAvgCommonWaitingTime; }
        }

        #endregion

        #region Constructor

        public FinishCollector() { }

        #endregion

        #region Methods

        public void ReceiveServiced(Request request)
        {
            lock (monitor)
            {
                _servicedRequests.Add(request);
            }
        }

        public void ReceiveRejected(Request request)
        {
            lock (monitor)
            {
                _rejectedRequests.Add(request);
            }
        }

        public void Update()
        {
            lock (monitor)
            {
                UpdateAvgCommonWaitingTime();
                UpdateAvgCommonServiceTime();
                UpdateCount();

                Release();
            }
        }

        private void UpdateAvgCommonServiceTime()
        {
            var servServiceTime = ServAvgCommonServicedTime * CountOfServiced;
            var rejServiceTime = RejAvgCommonServicedTime * CountOfRejected;

            var servNewServiceTime = _servicedRequests.Select(r => r.CommonServiceTime).Sum();
            var rejNewServiceTime = _rejectedRequests.Select(r => r.CommonServiceTime).Sum();

            var servCount = CountOfServiced + _servicedRequests.Count;
            var rejCount = CountOfRejected + _rejectedRequests.Count;

            if (servCount != 0)
            {
                ServAvgCommonServicedTime = (servServiceTime + servNewServiceTime) / servCount;
            }
            if (rejCount != 0)
            {
                RejAvgCommonServicedTime = (rejServiceTime + rejNewServiceTime) / rejCount;
            }
            if ((servCount + rejCount) != 0)
            {
                AvgCommonServicedTime = (ServAvgCommonServicedTime * servCount + RejAvgCommonServicedTime * rejCount) /
                                        (servCount + rejCount);
            }
        }

        private void UpdateAvgCommonWaitingTime()
        {
            var servWaitingTime = ServAvgCommonWaitingTime * CountOfServiced;
            var rejWaitingTime = RejAvgCommonWaitingTime * CountOfRejected;

            var servNewWaitingTime = _servicedRequests.Select(r => r.CommonWaitingTime).Sum();
            var rejNewWaitingTime = _rejectedRequests.Select(r => r.CommonWaitingTime).Sum();

            var servCount = CountOfServiced + _servicedRequests.Count;
            var rejCount = CountOfRejected + _rejectedRequests.Count;

            if (servCount != 0)
            {
                ServAvgCommonWaitingTime = (servWaitingTime + servNewWaitingTime) / servCount;
            }
            if (rejCount != 0)
            {
                RejAvgCommonWaitingTime = (rejWaitingTime + rejNewWaitingTime) / rejCount;
            }
            if ((servCount + rejCount) != 0)
            {
                AvgCommonWaitingTime = (ServAvgCommonWaitingTime * servCount + RejAvgCommonWaitingTime * rejCount) /
                                       (servCount + rejCount);
            }
        }

        private void UpdateCount()
        {
            CountOfServiced += _servicedRequests.Count;
            CountOfRejected += _rejectedRequests.Count;
        }

        private void Release()
        {
            lock (monitor)
            {
                _servicedRequests = new ConcurrentBag<Request>();
                _rejectedRequests = new ConcurrentBag<Request>();
            }
        }

        #endregion
    }
}